﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Dynamic;
using System.Linq.Expressions;
using System.Reflection;
using Telerik.Web.Mvc;
using Telerik.Web.Mvc.Infrastructure;
using TelerikMvcGridCustomBindingHelper.Aggregates;
using TelerikMvcGridCustomBindingHelper.DynamicQuery;
using TelerikMvcGridCustomBindingHelper.Mapper;
using TelerikMvcGridCustomBindingHelper.ORMIntegration;
using TelerikMvcGridCustomBindingHelper.Projections;
using TelerikMvcGridCustomBindingHelper.Util;

namespace TelerikMvcGridCustomBindingHelper
{
    /// <summary>
    /// Helper class to work with Telerik Grid for ASP.NET MVC using CustomBinding mode coupled with
    /// some mapping techniques to convert Entities objects into ViewModels objects.
    /// </summary>
    /// <typeparam name="TEntity">The type of your Entity class</typeparam>
    /// <typeparam name="TViewModel">The type of your ViewModel class</typeparam>
    public class GridCustomBindingHelper<TEntity, TViewModel> : BaseGridCustomBindingHelper<TEntity, TViewModel>
        where TViewModel : class
        where TEntity : class
    {
        #region Fields

        private readonly IQueryable<TEntity> _dataSource;
        private readonly IQueryable<TEntity> _dataSourceToCount;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="GridCustomBindingHelper{TEntity,TViewModel}"/> class.
        /// </summary>
        /// <param name="command">Telerik GridCommand containing informations about paging, filtering, sorting, aggregates</param>
        /// <param name="dataSource">IQueryable&lt;TEntity, TEntity&gt; to query the database</param>
        /// <param name="dataSourceToCount">IQueryable&lt;TEntity, TEntity&gt; only for count the total of elements in the database</param>
        /// <param name="defaultSortMemberName">Default sort member name for this entity</param>
        public GridCustomBindingHelper(GridCommand command, IQueryable<TEntity> dataSource, IQueryable<TEntity> dataSourceToCount, string defaultSortMemberName = null)
            : base(command, defaultSortMemberName)
        {
            _dataSource = dataSource;

            _dataSourceToCount = dataSourceToCount;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GridCustomBindingHelper{TEntity,TViewModel}"/> class.
        /// </summary>
        /// <param name="command">Telerik GridCommand containing informations about paging, filtering, sorting, aggregates</param>
        /// <param name="gridQueryProvider">GridQueryProvider containing the IQueryables to process and count data</param>
        public GridCustomBindingHelper(GridCommand command, GridQueryProvider gridQueryProvider)
            : this(command, gridQueryProvider.GetQuery<TEntity>(), gridQueryProvider.GetQuery<TEntity>(true)) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="GridCustomBindingHelper{TEntity,TViewModel}"/> class.
        /// </summary>
        /// <param name="command">Telerik GridCommand containing informations about paging, filtering, sorting, aggregates</param>
        /// <param name="dataSource">IQueryable&lt;TEntity, TEntity&gt; to query and count the total of elements in the database</param>
        public GridCustomBindingHelper(GridCommand command, IQueryable<TEntity> dataSource)
            : this(command, dataSource, null) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="GridCustomBindingHelper{TEntity,TViewModel}"/> class.
        /// </summary>
        /// <param name="command">Telerik GridCommand containing informations about paging, filtering, sorting, aggregates</param>
        public GridCustomBindingHelper(GridCommand command)
            : this(command, null, null) { }

        #endregion

        #region Helper Functions

        protected override void EnsureDataSourceIsProcessed()
        {
            if (IsDataSourceProcessed) return;

            using (var ormIntegrationHolder = new ORMIntegrationHolder())
            {
                try
                {
                    ormIntegrationHolder.BeginTransaction();

                    var data = _dataSource ?? ormIntegrationHolder.Query<TEntity>();

                    if (IgnoreGroupAggregatesField == false)
                        CommandField.GroupDescriptors.CreateAggregates(CommandField.Aggregates);

                    data = ApplyFiltering(data, CommandField.FilterDescriptors);

                    var dataSourceToCount = _dataSourceToCount ?? data;

                    TotalField = dataSourceToCount.Count();
                    AggregatesField = ApplyAggregates(dataSourceToCount, CommandField.Aggregates, TotalField);

                    ApplyDefaultSorting(CommandField, DefaultSortMemberName);

                    data = ApplySorting(data, CommandField.SortDescriptors, CommandField.GroupDescriptors);

                    data = ApplyProjectionsAndPagination(data);

                    ViewModelsField = ConvertEntitiesToViewModels(data);
                    var tempGroups = ApplyGrouping(ViewModelsField, CommandField.GroupDescriptors);

                    ProcessedDataField = FixFirstAndLastGroupAggregates(dataSourceToCount, tempGroups, CommandField.GroupDescriptors, CommandField.FilterDescriptors);

                    ormIntegrationHolder.CommitTransaction();
                }
                catch
                {
                    ormIntegrationHolder.RollBackTransaction();
                    throw;
                }
                finally
                {
                    IsDataSourceProcessed = true;
                }
            }
        }

        private IQueryable<TEntity> ApplyProjectionsAndPagination(IQueryable<TEntity> data)
        {
            if (UseProjectionsField)
            {
                var projectionsEngine = new ProjectionsEngine();
                var dynamicLinqSelector = projectionsEngine.BuildSelector<TEntity, TViewModel>(ProjectionsOptions);
                var projections = data.Select(dynamicLinqSelector.Expression);
                projections = ApplyPaging(projections, CommandField.Page, CommandField.PageSize);
                return projectionsEngine.GetData<TEntity, TViewModel>(projections).AsQueryable();
            }

            return (IQueryable<TEntity>)ApplyPaging(data, CommandField.Page, CommandField.PageSize);
        }

        private IEnumerable<TViewModel> ConvertEntitiesToViewModels(IEnumerable<TEntity> data)
        {
            EntitiesField = data.ToList();

            return GridModelMapper.Map<TEntity, TViewModel>(EntitiesField).ToList();
        }

        #endregion

        #region ApplyAggregates

        private IEnumerable FixFirstAndLastGroupAggregates(IQueryable<TEntity> queryable, IEnumerable groups, IList<GroupDescriptor> groupDescriptors, IList<IFilterDescriptor> filterDescriptors)
        {
            var aggregateFunctions = groups as IList<AggregateFunctionsGroup>;

            if (aggregateFunctions == null)
                return groups;

            if (aggregateFunctions.Count >= 1)
            {
                var firstGroup = aggregateFunctions.First();
                ((CustomAggregateFunctionsGroup)aggregateFunctions.First()).Aggregates = GetAggregatesForGroup(queryable, firstGroup, groupDescriptors, filterDescriptors);
            }

            if (aggregateFunctions.Count >= 2)
            {
                var lastGroup = aggregateFunctions.Last();
                ((CustomAggregateFunctionsGroup)aggregateFunctions.Last()).Aggregates = GetAggregatesForGroup(queryable, lastGroup, groupDescriptors, filterDescriptors);
            }

            return aggregateFunctions;
        }

        private Dictionary<string, object> GetAggregatesForGroup(IQueryable<TEntity> queryable, IGroup firstGroup, IList<GroupDescriptor> groupDescriptors, IList<IFilterDescriptor> filterDescriptors)
        {
            var filters = BuildGroupFilterDescriptors(firstGroup, groupDescriptors, filterDescriptors);

            var filteredGroupQuery = ApplyFiltering(queryable, filters);

            var aggregateDescriptors = BuildGroupAggregateDescriptors(groupDescriptors);

            return ApplyAggregates(filteredGroupQuery, aggregateDescriptors);
        }

        private Dictionary<string, object> ApplyAggregates(IQueryable<TEntity> queryable, IEnumerable<AggregateDescriptor> aggregateDescriptors, long? reusableCount = null)
        {
            var aggregates = new Dictionary<string, AggregatesContainer>();
            var isNHibernate = queryable.GetType().Assembly.FullName.Contains("NHibernate");

            foreach (var aggregateFunction in aggregateDescriptors.SelectMany(x => x.Aggregates))
            {
                string entityProperty;
                var viewModelProperty = aggregateFunction.SourceField;

                try
                {
                    entityProperty = GetGridPropertyInfo(viewModelProperty).PropertyPath;
                }
                catch
                {
                    entityProperty = viewModelProperty;
                }

                switch (aggregateFunction.AggregateMethodName)
                {
                    case "Sum":
                        aggregates.Push(viewModelProperty, queryable.Sum(GetPropertyExpression<TEntity, decimal?>(entityProperty)) ?? 0, AggregateMethod.Sum);
                        break;
                    case "Count":
                        reusableCount = reusableCount ?? queryable.Count();
                        aggregates.Push(viewModelProperty, reusableCount, AggregateMethod.Count);
                        break;
                    case "Average":
                        aggregates.Push(viewModelProperty, queryable.Average(GetPropertyExpression<TEntity, decimal?>(entityProperty)) ?? 0, AggregateMethod.Average);
                        break;
                    case "Min":
                        if (isNHibernate)
                            aggregates.Push(viewModelProperty, queryable.Min(GetPropertyExpression<TEntity, object>(entityProperty)) ?? 0, AggregateMethod.Min);
                        else
                            aggregates.Push(viewModelProperty, queryable.Min(GetPropertyExpression<TEntity, decimal?>(entityProperty)) ?? 0, AggregateMethod.Min);
                        break;
                    case "Max":
                        if (isNHibernate)
                            aggregates.Push(viewModelProperty, queryable.Max(GetPropertyExpression<TEntity, object>(entityProperty)) ?? 0, AggregateMethod.Max);
                        else
                            aggregates.Push(viewModelProperty, queryable.Max(GetPropertyExpression<TEntity, decimal?>(entityProperty)) ?? 0, AggregateMethod.Max);
                        break;
                    case "Custom":
                        if (ExpressionCustomAggregateFunctionField != null)
                            aggregates.Push(viewModelProperty, SafeInvokeAggregateExpression(queryable), AggregateMethod.Custom);
                        else if (InheritedClassCustomAggregateCalculatorField != null)
                            aggregates.Push(viewModelProperty, ((CustomAggregateFunction)InheritedClassCustomAggregateCalculatorField).GetValue(queryable), AggregateMethod.Custom);
                        break;
                }
            }

            return aggregates.ToDictionary(x => x.Key, x => (object)x.Value.GetValues());
        }

        #endregion

        #region Fluent API

        public new GridCustomBindingHelper<TEntity, TViewModel> AddValueResolver<TQueryValueResolver>(Expression<Func<TViewModel, object>> viewModelProperty) where TQueryValueResolver : class, IQueryValueResolver
        {
            return (GridCustomBindingHelper<TEntity, TViewModel>)base.AddValueResolver<TQueryValueResolver>(viewModelProperty);
        }

        public new GridCustomBindingHelper<TEntity, TViewModel> AddValueResolver<TQueryValueResolver>() where TQueryValueResolver : class, IQueryValueResolver
        {
            return (GridCustomBindingHelper<TEntity, TViewModel>)base.AddValueResolver<TQueryValueResolver>();
        }

        public new GridCustomBindingHelper<TEntity, TViewModel> AddValueResolver(IQueryValueResolver queryValueResolver)
        {
            return (GridCustomBindingHelper<TEntity, TViewModel>)base.AddValueResolver(queryValueResolver);
        }

        public new GridCustomBindingHelper<TEntity, TViewModel> UseCaseInsensitiveSearch(bool useCaseInsensitiveSearchOnBothClientAndServerSide = true)
        {
            return (GridCustomBindingHelper<TEntity, TViewModel>)base.UseCaseInsensitiveSearch(useCaseInsensitiveSearchOnBothClientAndServerSide);
        }

        public new GridCustomBindingHelper<TEntity, TViewModel> UseCaseInsensitiveSearch(Action<ICaseOptions<TEntity>> caseOptions)
        {
            return (GridCustomBindingHelper<TEntity, TViewModel>)base.UseCaseInsensitiveSearch(caseOptions);
        }

        /// <summary>
        /// Adds a custom aggregate function.
        /// </summary>
        /// <typeparam name="TAggregateFunction">A class that inherits from <see cref="CustomAggregateFunction"/>.</typeparam>
        /// <param name="targetProperty">The target property.</param>
        /// <returns>This same <see cref="GridCustomBindingHelper{TEntity,TViewModel}"/>.</returns>
        public GridCustomBindingHelper<TEntity, TViewModel> AddAggregateFunction<TAggregateFunction>(Expression<Func<TViewModel, object>> targetProperty) where TAggregateFunction : CustomAggregateFunction, new()
        {
            return (GridCustomBindingHelper<TEntity, TViewModel>)AddAggregateFunctionBase<TAggregateFunction>(targetProperty);
        }

        /// <summary>
        /// Adds a custom aggregate function.
        /// </summary>
        /// <param name="targetProperty">The target property.</param>
        /// <param name="entitiesFunction">An inline function that represents a custom aggregate function.</param>
        /// <param name="viewModelFunction">An inline function that represents a custom aggregate function.</param>
        /// <returns>This same <see cref="GridCustomBindingHelper{TEntity,TViewModel}"/>.</returns>
        public GridCustomBindingHelper<TEntity, TViewModel> AddAggregateFunction(Expression<Func<TViewModel, object>> targetProperty, Func<IQueryable<TEntity>, object> entitiesFunction, Func<IQueryable<TViewModel>, object> viewModelFunction = null)
        {
            return (GridCustomBindingHelper<TEntity, TViewModel>)AddAggregateFunctionBase(targetProperty, entitiesFunction, viewModelFunction);
        }

        #endregion

        #region ApplyPaging

        private static IQueryable ApplyPaging(IQueryable data, int pageIndex, int pageSize)
        {
            if (pageIndex > 1 && pageSize > 0)
            {
                var skip = (pageIndex - 1) * pageSize;
                data = data.Skip(skip).Take(pageSize);
            }
            else if (pageSize > 0)
            {
                data = data.Take(pageSize);
            }

            return data;
        }

        #endregion

        #region ApplySorting

        private IQueryable<TEntity> ApplySorting(IQueryable<TEntity> data, IList<SortDescriptor> sortDescriptors, IList<GroupDescriptor> groupDescriptors)
        {
            if (groupDescriptors.Any())
            {
                foreach (var groupDescriptor in groupDescriptors.Reverse())
                {
                    if (sortDescriptors.ToList().Exists(x => x.Member.Equals(groupDescriptor.Member)))
                    {
                        sortDescriptors.Remove(sortDescriptors.Single(x => x.Member.Equals(groupDescriptor.Member)));
                    }

                    var sortDescriptor = new SortDescriptor
                    {
                        Member = groupDescriptor.Member,
                        SortDirection = groupDescriptor.SortDirection
                    };

                    sortDescriptors.Insert(0, sortDescriptor);
                }
            }

            if (sortDescriptors.Any())
            {
                var isFirst = true;

                foreach (var sortDescriptor in sortDescriptors)
                {
                    var gridPropertyInfo = GetGridPropertyInfo(sortDescriptor.Member);

                    if (gridPropertyInfo.PropertyPath.IsNotNullOrWhiteSpace())
                    {
                        var addSortExpressionMethod = Reflector.GetMemberName<GridCustomBindingHelper<TEntity, TViewModel>>(x => AddSortExpression<object>(null, string.Empty, ListSortDirection.Ascending, false));

                        var method = GetType().GetMethod(
                            addSortExpressionMethod,
                            BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static,
                            Type.DefaultBinder,
                            new[] { typeof(IQueryable<TEntity>), typeof(string), typeof(ListSortDirection), typeof(bool) },
                            null);

                        var genericMethod = method.MakeGenericMethod(new[] { gridPropertyInfo.PropertyType });

                        data = genericMethod.Invoke(this, new object[] { data, gridPropertyInfo.PropertyPath, sortDescriptor.SortDirection, isFirst }) as IQueryable<TEntity>;
                    }
                    else
                    {
                        var customExpression = gridPropertyInfo.CustomExpression;

                        var exp = Expression.Lambda<Func<TEntity, object>>(customExpression.Body, customExpression.Parameters);
                        data = AddSortExpression(data, exp, sortDescriptor.SortDirection, isFirst);
                    }

                    isFirst = false;
                }
            }

            return data;
        }

        // This method has to be protected so we can call it with reflection
        // ReSharper disable MemberCanBePrivate.Global
        protected IQueryable<TEntity> AddSortExpression<TProperty>(IQueryable<TEntity> data, string sortMember, ListSortDirection sortDirection, bool isFirst)
        {
            var exp = GetPropertyExpression<TEntity, TProperty>(sortMember);
            return AddSortExpression(data, exp, sortDirection, isFirst);
        }
        // ReSharper restore MemberCanBePrivate.Global

        private static IQueryable<TEntity> AddSortExpression<TProperty>(IQueryable<TEntity> data, Expression<Func<TEntity, TProperty>> propertySelector, ListSortDirection sortDirection, bool isFirst)
        {
            if (sortDirection == ListSortDirection.Ascending)
            {
                return isFirst
                    ? data.OrderBy(propertySelector)
                    : ((IOrderedQueryable<TEntity>)data).ThenBy(propertySelector);
            }

            return isFirst
                ? data.OrderByDescending(propertySelector)
                : ((IOrderedQueryable<TEntity>)data).ThenByDescending(propertySelector);
        }

        #endregion

        #region ApplyFiltering

        private IQueryable<TEntity> ApplyFiltering(IQueryable<TEntity> data, IList<IFilterDescriptor> filterDescriptors, FilterCompositionLogicalOperator logicalOperator = FilterCompositionLogicalOperator.And)
        {
            if (filterDescriptors.Any())
            {
                if (GridModelMapper.ConfigurationObject.ShouldFallBackToLinqExpressionBuilder)
                {
                    data = ApplyFilteringWithDynamicWhereClause(data, filterDescriptors, logicalOperator);
                }
                else
                {
                    data = ApplyFilteringWithExpressionWhereClause(data, filterDescriptors, logicalOperator);
                }
            }

            return data;
        }

        private IQueryable<TEntity> ApplyFilteringWithExpressionWhereClause(IQueryable<TEntity> data, IList<IFilterDescriptor> filterDescriptors, FilterCompositionLogicalOperator logicalOperator = FilterCompositionLogicalOperator.And)
        {
            if (filterDescriptors.Any())
            {
                var dynamicWhereClause = new ExpressionWhereClause<TEntity, TViewModel>();

                foreach (var filterDescriptor in filterDescriptors.Reverse())
                {
                    if (filterDescriptor is CompositeFilterDescriptor)
                    {
                        var compositeFilterDescriptor = filterDescriptor as CompositeFilterDescriptor;
                        data = ApplyFilteringWithExpressionWhereClause(data, compositeFilterDescriptor.FilterDescriptors, compositeFilterDescriptor.LogicalOperator);
                    }
                    else
                    {
                        var filter = (FilterDescriptor)filterDescriptor;
                        var whereClause = new ExpressionWhereClause<TEntity, TViewModel>(filter, CaseInsensitiveField, AcceptNullValuesWhenFilteringField, QueryValueResolvers);

                        if (logicalOperator == FilterCompositionLogicalOperator.And)
                        {
                            dynamicWhereClause.AndAlso(whereClause);
                        }
                        else
                        {
                            dynamicWhereClause.OrElse(whereClause);
                        }
                    }
                }

                if (dynamicWhereClause.HasFilters())
                {
                    data = data.Where(dynamicWhereClause.Predicate);
                }
            }

            return data;
        }

        private IQueryable<TEntity> ApplyFilteringWithDynamicWhereClause(IQueryable<TEntity> data, IList<IFilterDescriptor> filterDescriptors, FilterCompositionLogicalOperator logicalOperator = FilterCompositionLogicalOperator.And)
        {
            if (filterDescriptors.Any())
            {
                var dynamicWhereClause = new DynamicWhereClause<TEntity, TViewModel>();

                foreach (var filterDescriptor in filterDescriptors.Reverse())
                {
                    if (filterDescriptor is CompositeFilterDescriptor)
                    {
                        var compositeFilterDescriptor = filterDescriptor as CompositeFilterDescriptor;
                        data = ApplyFilteringWithDynamicWhereClause(data, compositeFilterDescriptor.FilterDescriptors, compositeFilterDescriptor.LogicalOperator);
                    }
                    else
                    {
                        var filter = (FilterDescriptor)filterDescriptor;
                        var whereClause = new DynamicWhereClause<TEntity, TViewModel>(filter, CaseInsensitiveField, AcceptNullValuesWhenFilteringField, QueryValueResolvers);

                        if (logicalOperator == FilterCompositionLogicalOperator.And)
                        {
                            dynamicWhereClause.AndAlso(whereClause);
                        }
                        else
                        {
                            dynamicWhereClause.OrElse(whereClause);
                        }
                    }
                }

                if (dynamicWhereClause.HasFilters())
                {
                    data = data.Where(dynamicWhereClause.CustomExpression);
                }
            }

            return data;
        }

        #endregion

        /// <summary>
        /// An abstract class to help create a custom aggregate function.
        /// </summary>
        public abstract class CustomAggregateFunction : CustomAggregateFunctionBase
        {
            private IList<TEntity> _entities;
            private object _groupKeyValue;
            private string _groupKeyName;

            protected internal override object GetValueFromGroup(IEnumerable<TViewModel> viewModelsGroup, IList<TEntity> entitiesField, object groupKeyValue, string groupKeyName)
            {
                _entities = entitiesField;
                _groupKeyValue = groupKeyValue;
                _groupKeyName = groupKeyName;

                return GetValueFromGroup(viewModelsGroup);
            }

            /// <summary>
            /// A custom aggregate function that will be executed against a grouped set of entities.
            /// </summary>
            /// <param name="viewModelsGroup">The IQueryable&lt;TViewModel&gt; source group.</param>
            /// <returns>The result of the aggregate function.</returns>
            protected virtual object GetValueFromGroup(IEnumerable<TViewModel> viewModelsGroup)
            {
                var entities = _entities != null
                    ? _entities.AsQueryable().Where(string.Format("{0} == @0", _groupKeyName), _groupKeyValue)
                    : GridModelMapper.Map<TViewModel, TEntity>(viewModelsGroup).AsQueryable();

                return GetValue(entities.AsQueryable());
            }

            /// <summary>
            /// A custom aggregate function.
            /// </summary>
            /// <param name="entities">The IQueryable&lt;TEntity&gt; data source.</param>
            /// <returns>The result of the aggregate function.</returns>
            protected internal abstract object GetValue(IQueryable<TEntity> entities);
        }
    }

    /// <summary>
    /// Helper class to work with Telerik Grid for ASP.NET MVC using CustomBinding mode
    /// </summary>
    /// <typeparam name="TEntity">The type of your Entity class</typeparam>
    public class GridCustomBindingHelper<TEntity> : GridCustomBindingHelper<TEntity, TEntity> where TEntity : class
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="GridCustomBindingHelper{TEntity}"/> class.
        /// Default constructor
        /// </summary>
        /// <param name="command">Telerik GridCommand containing informations about paging, filtering, sorting, aggregates</param>
        /// <param name="dataSource">IQueryable&lt;TEntity, TEntity&gt; to query the database</param>
        /// <param name="dataSourceToCount">IQueryable&lt;TEntity, TEntity&gt; only for count the total of elements in the database</param>
        /// <param name="defaultSortMemberName">Default sort member name for this entity</param>
        public GridCustomBindingHelper(GridCommand command, IQueryable<TEntity> dataSource, IQueryable<TEntity> dataSourceToCount, string defaultSortMemberName)
            : base(command, dataSource, dataSourceToCount, defaultSortMemberName) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="GridCustomBindingHelper{TEntity}"/> class.
        /// Simplified constructor
        /// </summary>
        /// <param name="command">Telerik GridCommand containing informations about paging, filtering, sorting, aggregates</param>
        /// <param name="gridQueryProvider">GridQueryProvider containing the IQueryables to process and count data</param>
        public GridCustomBindingHelper(GridCommand command, GridQueryProvider gridQueryProvider)
            : base(command, gridQueryProvider) { }

        /// <summary>
        /// Minimal constructor
        /// Initializes a new instance of the <see cref="GridCustomBindingHelper{TEntity}"/> class. 
        /// </summary>
        /// <param name="command">Telerik GridCommand containing informations about paging, filtering, sorting, aggregates</param>
        /// <param name="dataSource">IQueryable&lt;TEntity, TEntity&gt; to query and count the total of elements in the database</param>
        public GridCustomBindingHelper(GridCommand command, IQueryable<TEntity> dataSource)
            : base(command, dataSource) { }
    }

    /// <summary>
    /// Helper class to provide common useful functionality while working with Telerik MVC Grid
    /// </summary>
    public static class GridCustomBindingHelper
    {
        #region Public Methods

        /// <summary>
        /// Serializes the FilterDescriptors provided with the GridCommand object by the Telerik Grid.
        /// Useful for persisting filter selection.
        /// </summary>
        /// <param name="descriptors">FilterDescriptors provided with the GridCommand object by the Grid</param>
        /// <returns>filters in XML formatted string</returns>
        public static string SerializeFilters(IEnumerable<IFilterDescriptor> descriptors)
        {
            if (descriptors == null)
                return null;

            var dlist = FiltersDeInterface(descriptors).ToList();
            return dlist.ToXml();
        }

        /// <summary>
        /// Deserializes the filters that were serialized with SerializeFilters method
        /// </summary>
        /// <param name="filters">string containing the serialized filters in XML format</param>
        /// <returns>Enumeration of deserialized FilterDescriptorObjects</returns>
        public static IEnumerable<FilterDescriptor> DeSerializeFilters(string filters)
        {
            if (String.IsNullOrWhiteSpace(filters))
                return null;

            var result = filters.FromXml<FilterDescriptor[]>();
            return result;
        }

        #endregion

        #region Helper Methods

        /// <summary>
        /// finds all user specified FilterDescriptors and returns the list.  Useful for serialization
        /// since interfaces can't be serialized
        /// </summary>
        /// <param name="descriptors">list of filter descriptor interfaces</param>
        /// <returns>filter descriptors without interfaces</returns>
        private static IEnumerable<FilterDescriptor> FiltersDeInterface(IEnumerable<IFilterDescriptor> descriptors)
        {
            foreach (var f in descriptors)
            {
                var fd = f as FilterDescriptor;
                if (fd != null)
                    yield return fd;

                //if combined filter go into recursion
                var cfd = f as CompositeFilterDescriptor;
                if (cfd != null)
                    foreach (var f2 in FiltersDeInterface(cfd.FilterDescriptors))
                        yield return f2;
            }
        }

        #endregion
    }
}